/* File : nidaqmx.i */
%module nidaqmx
%{
#include <NIDAQmx.h>
%}
%include <windows.i>
//%include <typemaps.i>
%include <exception.i>
%include <nidaqmx_const.i>
%feature("autodoc", "0");

typedef uInt32*              TaskHandle;

/* Set a default for the 'reserved' argument, so I can omit it in Python calls*/
%typemap(default) bool32 *reserved { $1 = NULL; }

/**********************************************************************
 * Convert DAQmx return codes to Exceptions
 **********************************************************************/
%exception {
    $action
    if ( DAQmxFailed(result) ) { RaiseError(); SWIG_fail; }
}
%{
    static void RaiseError() {
        char errBuff[2048]={'\0'};
        DAQmxGetExtendedErrorInfo(errBuff,2048);
        SWIG_exception(SWIG_RuntimeError, errBuff);
        fail:
        return;
    }
%}
/**********************************************************************
 * Enable Creation and Deletion of TaskHandles
 **********************************************************************/
%typemap(in, numinputs=0) TaskHandle *OUTPUT ($*1_type temp) {
    $1 = &temp;
}
%typemap(argout) TaskHandle *OUTPUT {
    $result = SWIG_NewPointerObj(SWIG_as_voidptr(*$1), $*1_descriptor, 0 );
}
%feature("autodoc", "handle = CreateTask(taskName)") DAQmxCreateTask;
%feature("autodoc", "handle = StopTask(handle)") DAQmxStopTask;

/**********************************************************************
 * Enable return of basic data types
 **********************************************************************/
%typemap(in, numinputs=0) uInt32 *data ($*1_type temp), 
        int32 *data ($*1_type temp), float64 *data ($*1_type temp) {
    $1 = &temp;
}
%typemap(argout) uInt32 *data, int32 *data, float64 *data {
    $result = SWIG_From_long(*$1);
}
//TODO: put in genInterface: %feature("autodoc", "handle = CreateTask(taskName)") DAQmxCreateTask;

/**********************************************************************
 * Allow DAQmxWrite* to accept lists
 **********************************************************************/
//Convert a list to an Integer array for output
%typemap(in) uInt8 writeArray[] {
    Py_ssize_t sz, i;
    $*1_type *lst_ptr;
    
    sz = PyList_Size($input);
    lst_ptr = $1 = ($*1_type *) malloc(sizeof($*1_type)*sz);
    
    for (i = 0; i < sz; i++, lst_ptr++) {
        PyObject *s = PyList_GetItem($input,i);
        if (!PyInt_Check(s)) {
            free($1);
            PyErr_SetString(PyExc_ValueError, "List items must be an Int");
            return NULL;
        }
        *lst_ptr = PyInt_AsLong(s);
    }
}
%typemap(freearg) uInt8 writeArray[] {
   if ($1) free($1);
}

/* Copy all three typemap modes (in,argout, and freearg) the other integer types */
%apply uInt8 writeArray[] { int16 writeArray[] };
%apply uInt8 writeArray[] { int32 writeArray[] };
%apply uInt8 writeArray[] { uInt16 writeArray[] };
%apply uInt8 writeArray[] { uInt32 writeArray[] };

//Repeat for float64
%typemap(in) float64 writeArray[] {
    Py_ssize_t sz, i;
    $*1_type *lst_ptr;
    
    sz = PyList_Size($input);
    lst_ptr = $1 = ($*1_type *) malloc(sizeof($*1_type)*sz);
    if ($1 == NULL) {
        PyErr_NoMemory();
        SWIG_fail;
    }

    for (i = 0; i < sz; i++, lst_ptr++) {
        PyObject *s = PyList_GetItem($input,i);
        if (!PyFloat_Check(s)) {
            free($1);
            PyErr_SetString(PyExc_ValueError, "All list items must be Floats");
            return NULL;
        }
        *lst_ptr = PyFloat_AsDouble(s);
    }
}
%typemap(freearg) float64 writeArray[] {
   if ($1) free($1);
}

//Do not require sampsPerChanWritten as an input
%typemap(in, numinputs=0) int32 *sampsPerChanWritten ($*1_type temp) {
    $1 = &temp;
}
//Instead make sampsPerChanWritten the return value
%typemap(argout) int32 *sampsPerChanWritten {
    $result = PyInt_FromLong(*$1);
}

/**********************************************************************
 * Allow DAQmxRead* to return data as lists
 **********************************************************************/

/* Don't require sampsPerChanRead as an input argument. In fact, this
 * information will be discarded.
 */
%typemap(in, numinputs=0) int32 *sampsPerChanRead ($*1_type tmp) {
    $1 = &tmp;
}

/* Don't require readArray as an input argument
 * Instead, use arraySizeInSamps to create an empty array to be filled.
 */
%typemap(in) (float64 readArray[], uInt32 arraySizeInSamps) {
    $2 = PyInt_AsLong($input);
    $1 = ($*1_type *) malloc(sizeof($*1_type)*$2);
}
%typemap(in) (int16 readArray[], uInt32 arraySizeInSamps) = (float64 readArray[], uInt32 arraySizeInSamps);

/* Build a Python list for returning the data */
%typemap(argout) (float64 readArray[], uInt32 arraySizeInSamps) {
    $2_type sz, i;
    sz = PyInt_AsLong($input);
    $result = PyList_New(sz);
    for(i=0; i < sz; i++) {
        PyList_SetItem($result, i, PyFloat_FromDouble($1[i]));
    }
    //TODO: I wish I could use PyList_FromReturn() instead, but need 
    //      task handle or channel count
}
%typemap(argout) (int16 readArray[], uInt32 arraySizeInSamps) {
    $2_type sz, i;
    sz = PyInt_AsLong($input);
    $result = PyList_New(sz);
    for(i=0; i < sz; i++) {
        /* The PyFloat_FromLong() is why I need to declare this separate
         * from the float64 declaration above */
        PyList_SetItem($result, i, PyInt_FromLong($1[i]));
    }
}
/* Release the memory allocated for the array */
%typemap(freearg) (float64 readArray[], uInt32 arraySizeInSamps) {
    free(($*1_type *) $1);
}
%typemap(freearg) (int16 readArray[], uInt32 arraySizeInSamps) = (float64 readArray[], uInt32 arraySizeInSamps);

/* Copy all three typemap modes (in,argout, and freearg) the other integer types */
%apply (int16 readArray[], uInt32 arraySizeInSamps) { (int32 readArray[], uInt32 arraySizeInSamps) };
%apply (int16 readArray[], uInt32 arraySizeInSamps) { (uInt8 readArray[], uInt32 arraySizeInSamps) };
%apply (int16 readArray[], uInt32 arraySizeInSamps) { (uInt16 readArray[], uInt32 arraySizeInSamps) };
%apply (int16 readArray[], uInt32 arraySizeInSamps) { (uInt32 readArray[], uInt32 arraySizeInSamps) };

/* Now support scalar reads */
%typemap(in, numinputs=0) float64 *value ($*1_type temp), uInt32 *value ($*1_type temp) {
    $1 = &temp;
}
%typemap(argout) float64 *value { $result = PyFloat_FromDouble(*$1);}
%typemap(argout) uInt32 *value  { $result = PyInt_FromLong(*$1);    }


/**********************************************************************
 * EveryN callback issues
 **********************************************************************/

// http://www.swig.org/Doc1.3/SWIGDocumentation.html#SWIG_nn30
%feature("autodoc", "RegisterEveryNSamplesEvent(task, everyNsamplesEventType, nSamples, options, C_callbackFunction, Python_callbackFunction) -> int32") RegisterEveryNSamplesEvent;
%feature("docstring", "Call an event every N Samples. C_callbackFunction may be EveryNCallPython_cb or ReadAnalogF64_cb. Python_callbackFunction depends on the C Callback selected.") DAQmxRegisterEveryNSamplesEvent;
%{
    //SWIG descriptor for TaskHandle.
    //TODO: Would be great to get SWIG to write this for me
    #define TaskHandleDescriptor    SWIGTYPE_p_unsigned_long
    
    /*
     * EveryNCallPython_cb is the generic function to be used with
     *  RegisterEveryNSamplesEvent. It simply assumes that callbackData
     *  is a Python function and calls that function for each event.
     * 
     * Usage in Python:
     * >> DAQmx.everyNsamplesEventType(h, DAQmx.Val_Acquired_Into_Buffer,\
     *           1000,0, DAQmx.EveryNCallPython_cb,EveryNCallback)
     * - Every time 1000 samples are read into the DAQ buffer 
     *      EveryNCallPython_cb will be called
     * - EveryNCallPython_cb then call the Python function EveryNCallback
     * - EveryNCallback is a Python function that takes the following arguments:
     *   - taskHandle - Handle of reading task
     *   - everyNsamplesEventType - Same EventType given to everyNsamplesEventType
     *   - nSamples - Number of samples expected to read from DAQ buffer
     * 
     * @param taskHandle: Handle of reading task
     * @param everyNsamplesEventType: Callback type.
     * @param nSamples: Number of samples to read
     * @param python_cb: Python Function to call with data that has been read.
     * @return: Standard DAQmx status code
     * 
     * TODO:
     * - Consider adding support for EveryNCallback or
     *   (EveryNCallback, data) in last argument of everyNsamplesEventType
     */
    int32 EveryNCallPython_cb(TaskHandle taskHandle, 
            int32 everyNsamplesEventType, uInt32 nSamples, 
            void *python_cb) {
        int32 err;            // Samples/Chan read and return error code
        PyObject *func, *arglist;   // Python callback and argument list
        PyObject *result;           // Return value from Python callback
        
        //Get a Global Interpreter Lock
        //See: http://docs.python.org/c-api/init.html#thread-state-and-the-global-interpreter-lock
        PyGILState_STATE gstate;
        gstate = PyGILState_Ensure(); 
                
        //Call the Python function
        func = (PyObject *) python_cb;
        if (!PyCallable_Check(func)) {
            SWIG_exception(SWIG_RuntimeError, "Need a callable object!");
            SWIG_fail;
        }
        arglist = Py_BuildValue("(N,l,l)",      // Build argument list
                    SWIG_NewPointerObj(SWIG_as_voidptr(taskHandle),
                        TaskHandleDescriptor, 0 ),
                    everyNsamplesEventType, nSamples);

        result = PyEval_CallObject(func,arglist); // Call Python
        
        Py_DECREF(arglist);     // Trash arglist
        if (result == NULL) {   //Check for errors in func
            err = DAQmxErrorRuntimeAborting_Routing;
            SWIG_fail;
        } else if (result) {
            err = PyLong_AsLong(result);
        }
        Py_XDECREF(result);                         // Discard result
        
        PyGILState_Release(gstate); // Release thread lock
        
        return err;
        
        fail:
            //There seems to be no way to raise a Python error
            //from here. This is the next best thing I can find:
            PyErr_Print();
            DAQmxStopTask(taskHandle);
            printf("Stopping task because of error.\n");
            return err;
    }

    /*
     * ReadAnalogF64_cb is a C callback function that can be 
     * supplied to RegisterEveryNSamplesEvent(). The goal of 
     * ReadAnalogF64_cb() is to provide fast analog reads from the DAQ.
     * 
     * Using EveryNCallPython_cb an analog read would look like this:
     * ___________C Code___________|_Python Code________
     * RegisterEveryNSamplesEvent  |
     *      \                      |
     * EveryNCallPython_cb --------|---> EveryNCallback
     *                             |      /
     * ReadAnalogF64  <------------|------
     *  \                          |
     *   ---(make list & return)---|---> EveryNCallback
     * 
     * ReadAnalogF64_cb simplifies the process to:
     * ___________C Code___________|_Python Code________
     * RegisterEveryNSamplesEvent  |
     *      \                      |
     * ReadAnalogF64_cb            |
     *  \                          |
     *   ---(make list & call)-----|---> EveryNCallback
     * 
     * Usage in Python:
     * >> DAQmx.RegisterEveryNSamplesEvent(h, DAQmx.Val_Acquired_Into_Buffer,\
     *           1000,0, DAQmx.ReadAnalogF64_cb,EveryNCallback)
     * - Every time 1000 samples are read into the DAQ buffer 
     *      ReadAnalogF64_cb will be called
     * - ReadAnalogF64_cb will read the floating point data from the 
     *      channels, using DAQmx_Val_Auto and DAQmx_Val_GroupByChannel
     * - The data will be converted to a Python list and the
     *      Python function EveryNCallback will be called with the list
     * - EveryNCallback is a Python function that takes the following arguments:
     *   - sampsPerChanRead - This is the sampsPerChanRead returned by DAQmxReadAnalogF64
     *   - dataRead - List with data read from DAQ
     * 
     * @param taskHandle: Handle of reading task
     * @param everyNsamplesEventType: Callback type. Must be DAQmx_Val_Acquired_Into_Buffer
     * @param nSamples: Number of samples to read
     * @param python_cb: Python Function to call with data that has been read.
     * @return: Standard DAQmx status code
     */
    int32 ReadAnalogF64_cb(TaskHandle taskHandle, 
            int32 everyNsamplesEventType, uInt32 nSamples, 
            void *python_cb) {
        float64 *readArray;         //Will hold the raw data read from DAQ
        PyObject *dataOut;          // Python list made from readArray
        int32 read, err;            // Samples/Chan read and return error code
        uInt32 chCnt,i;             // Channel Count, Loop Index
        PyObject *func, *arglist;   // Python callback and argument list
        PyObject *result;           // Return value from Python callback
        
        //Get a Global Interpreter Lock
        //See: http://docs.python.org/c-api/init.html#thread-state-and-the-global-interpreter-lock
        PyGILState_STATE gstate;
        gstate = PyGILState_Ensure(); 

        //Validate Event Type
        if (everyNsamplesEventType != DAQmx_Val_Acquired_Into_Buffer) {
            SWIG_exception(SWIG_ValueError, "ReadAnalogF64_cb only works for input tasks");
        } 
        
        //Determine how many channels there are in the task
        DAQmxGetReadNumChans(taskHandle, &chCnt);
        
        //Make space for data and fill the space from the DAQ buffer
        readArray = (float64 *) malloc(sizeof(float64)*nSamples*chCnt);
        if (readArray == NULL) {
            PyErr_NoMemory();
            err = DAQmxErrorRuntimeAborting_Routing;
            SWIG_fail;
        }
        err = DAQmxReadAnalogF64(taskHandle,DAQmx_Val_Auto,10.0,
            DAQmx_Val_GroupByChannel,readArray,nSamples*chCnt,&read,NULL);
        if ( DAQmxFailed(err) ) { RaiseError(); SWIG_fail; }
        
        //Convert read data to a list
        //NOTE: Use read, because it tells how much was actually read,
        //      So we can ommit empty buffer space
        //TODO: Use PyList_FromReturn() instead
        dataOut = PyList_New(read*chCnt);
        for(i=0; i < read*chCnt; i++) {
            if ( PyList_SetItem(dataOut, i, PyFloat_FromDouble(readArray[i])) != 0) { 
                if( PyErr_Occurred() != NULL) {
                    SWIG_fail; //Handle as regular Python error
                } else {
                    //Return some sort of feedback
                    printf("Assignement error for data[%d]=%f", i, readArray[i]);
                    break;
                }
            }
        }
                
        //Call the Python function
        func = (PyObject *) python_cb;
        if (!PyCallable_Check(func)) {
            SWIG_exception(SWIG_RuntimeError, "Need a callable object!");
            SWIG_fail;
        }
        arglist = Py_BuildValue("(l,O)",read, dataOut);// Build argument list

        result = PyEval_CallObject(func,arglist); // Call Python
        
        Py_DECREF(arglist);     // Trash arglist
        Py_DECREF(dataOut);     // This function doesn't need data list. Necissary?
        if (result == NULL) {   //Check for errors in func
            err = DAQmxErrorRuntimeAborting_Routing;
            SWIG_fail;
        } else if (result) {
            err = PyLong_AsLong(result);
        }
        Py_XDECREF(result);                         // Discard result
        
        PyGILState_Release(gstate); // Release thread lock
        
        free( readArray );
        return err;
        
        fail:
            free( readArray );
            //There seems to be no way to raise a Python error
            //from here. This is the next best thing I can find:
            PyErr_Print();
            DAQmxStopTask(taskHandle);
            printf("Stopping task because of error.\n");
            return err;
    }
    //TODO: Build a digital corelary to ReadDigitalU32_cb
    
    /* Take the return from DAQmxReadAnalogF64() and segment it into
     * Python lists. Note this will separate channel data into separate lists.
     * @param readArray:        Pointer to array of data read by DAQmxReadAnalogF64
     * @param sampsPerChanRead: Value of same name returned by DAQmxReadAnalogF64
     * @param chCnt:            Number of channels in this task
     * @return: If chCnt > 1: [[ch1 Data], [ch2 Data], etc.]
     *          If chCn == 1 : [ch1 Data]
     *          If error : NULL
     */
    PyObject *PyList_FromReturn(float64 *readArray,
                                int32 sampsPerChanRead, uInt32 chCnt) {
        PyObject *dataOut, *lst;
        float64 *arrPnt=readArray;
        uInt32 ch;
        int32 i;
        
        dataOut = PyList_New(chCnt);
        for(ch=0; ch < chCnt; ch++) {
            lst = PyList_New(sampsPerChanRead);
            for(i=sampsPerChanRead*ch; i < sampsPerChanRead*(ch+1); i++, arrPnt++) {
                if ( PyList_SetItem(lst, i, PyFloat_FromDouble(*arrPnt)) != 0) { 
                    if( PyErr_Occurred() != NULL) { //TODO: PyErr_Occurred == NULL but SetItem still failed?
                        SWIG_fail; //Handle as regular Python error
                    } else {
                        //Failed to raise Python error, return some sort of feedback
                        printf("Assignement error for data[%d]=%f", i, readArray[i]);
                        break;
                    }
                }
            }
            PyList_SetItem(dataOut, ch, lst);
        }
        
        if (chCnt > 1) {
            return dataOut; //List of lists
        } else {
            return lst; //Single channel, return 1D list
        }
        fail: //Error
            return NULL;
    }
%}

//Make sure EveryNCallPython_cb & ReadAnalogF64_cb show up on the Python side
%constant int32 EveryNCallPython_cb(TaskHandle, int32, uInt32, void*);
%constant int32 ReadAnalogF64_cb(TaskHandle, int32, uInt32, void*);

// Handle incoming pointer to C function callback
%typemap(in) void *callbackData {
    if (!PyCallable_Check($input)) {
        SWIG_exception(SWIG_RuntimeError, "callbackData must be a callable object.");
        SWIG_fail;
    }
    $1 = $input;
    Py_XINCREF($1); // Add a reference to new callback
}

%inline %{
    /*
     * Call_ReadAnalogF64_cb simply allows the user to call
     * ReadAnalogF64_cb directly from Python. This is primarily used
     * for testing, but may also be used to allow re-use of a Python
     * callback function for single reads.
     */
    int32 Call_ReadAnalogF64_cb(TaskHandle taskHandle, uInt32 nSamples, void *callbackData) {
        int32 err;
        err = ReadAnalogF64_cb(taskHandle, DAQmx_Val_Acquired_Into_Buffer,
         nSamples, callbackData);
    return err;
    }
%}


/*
 * TODO:
 * - Create handlers for 
 *  - DAQmxRegisterDoneEvent(TaskHandle task, uInt32 options, DAQmxDoneEventCallbackPtr callbackFunction, void *callbackData);
 *  - DAQmxRegisterSignalEvent(TaskHandle task, int32 signalID, uInt32 options, DAQmxSignalEventCallbackPtr callbackFunction, void *callbackData);

/**********************************************************************
 * OS Dependant Defines
 **********************************************************************/

#ifdef __linux__
#define __CFUNC
#define __CFUNC_C
#define __CFUNCPTRVAR
#define CVICDECL
#define CVICALLBACK     CVICDECL
#else
#define __CFUNC         __stdcall
#define __CFUNC_C       __cdecl
#define __CFUNCPTRVAR   __cdecl
#define CVICDECL        __cdecl
#define CVICALLBACK     CVICDECL
#endif

#ifdef __linux__
	typedef long long int      int64;
#else
	typedef __int64            int64;
#endif
#ifdef __linux__
	typedef unsigned long long uInt64;
#else
	typedef unsigned __int64   uInt64;
#endif

/* END nidaqmx_template.i */

/*
 * TODO:
 * - Remove the header file top comment block
 * - Deal with __linux__ remainders
 * - Remove TaskHandle def
 */
